/*
Document: js2k.js
Created on: 03-dic-2008, 11:08:16
Version: 0.1
Author: Andrés Nieto Porras
Description: Core de JS2K, funciones necesarias y básicas para unir todas las
  funcionalidades del framework.
*/
var js2k = {
   /*
Property: Class
          Añade la posibilidad de generar clases en Javascript, emulando la herencia.
Arguments:
        - opt:  Atributos, métodos y funcionalidades que compondrán la clase.

Returns:
        Una Clase que podrá ser usada para obtener objetos apartir de dicha clase.
Example:
      var Human = new js2k.Class({
          name: null,
          init: function(name){
            this.name = name;
          },
          presentarse: function(){
            alert("Hola, soy " + this.name);
          }
      });

    var Juan = new Humano("Juan");
    Juan.prensentar();
*/
    Class: function(opt){
      // Creamos Class
      var _class = function(){
        return (arguments[0] !== null &&
                this.init && typeof this.init == 'function') ? this.init.apply(this,arguments): this;
      }

      // Extendemos las propiedades
      for (var p in this) _class[p] = this[p];
      _class.prototype = opt;
      _class.constructor = js2k.Class;

      // Extendemos la clase
      _class.Extend = function(properties){
        var proto = new this(null);
        for (var property in properties){
            var tmp = proto[property];
            proto[property] = this.Merge(tmp,properties[property]);
        }
        return new js2k.Class(proto);
      }
      // Implementamos nuevos métodos
      _class.Implement = function(){
        for (var i = 0, l = arguments.length; i < l; i++)
          js2k.extend(arguments[i],this.prototype);
      }
      _class.Merge = function(a,b){
          if (a && a != b){
              var type = typeof b;
              if (type != typeof a) return b;
              switch(type){
                  case 'function':
                        var m = function(){
                            this.parent = arguments.callee.parent;
                            return b.apply(this, arguments);
                        };
                        m.parent = a;
                        return m;
                  case 'object':
                        return js2k.merge(a,b);
              }
          }
          return b;
      }
      return _class;
    },
    merge: function(){
       var m = {};
       for (var i = 0; i < arguments.length; i++){
        for (var x in arguments[i]) {
          var a = arguments[i][x];
          var b = m[x];
          if (b && typeof a == 'object' && typeof b == 'object')
                m[x] = js2k.merge(b,a);
          else
                m[x] = a;
        }
       }
       return m;
    },
    get: function(selector, contexto){
      // Attempt to fail gracefully in lesser browsers
	  if (!document.getElementsByTagName) return new Array();
   	  if (document.querySelectorAll) return js2k.toArray(document.querySelectorAll(selector));
	  var getAllChildren = function(e) { return e.all ? e.all : e.getElementsByTagName('*');}

	  // Split selector in to tokens
	  var tokens = selector.split(' ');
	  var currentContext = new Array(document);
	  tokens.each(function(token){
	    var token = token.trim();
	    if (token.indexOf('#') > -1) {
	      // Token is an ID selector
	      var bits = token.split('#');
	      var tagName = bits[0];
	      var id = bits[1];
	      var element = document.getElementById(id);
	      if (tagName && element.nodeName.toLowerCase() != tagName) {
	        // tag with that ID not found, return false
	        return new Array();
	      }
	      // Set currentContext to contain just this element
	      currentContext = new Array(element);
	      return; // Skip to next token
	    }
	    if (token.indexOf('.') > -1) {
	      // Token contains a class selector
	      var bits = token.split('.');
	      var tagName = bits[0];
	      var className = bits[1];
	      if (!tagName) {
	        tagName = '*';
	      }
	      // Get elements matching tag, filter them for class selector
	      var found = new Array;
	      var foundCount = 0;
              currentContext.each(function(context){
	        var elements = (tagName == '*')?getAllChildren(context):context.getElementsByTagName(tagName);
	        js2k.toArray(elements).each(function(element){
	          found[foundCount++] = element;
	        });
	      })
	      currentContext = new Array;
	      var currentContextIndex = 0;
              found.each(function(found){
	        if (found.className && found.className.match(new RegExp('\\b'+className+'\\b'))) {
	          currentContext[currentContextIndex++] = found;
	        }
	      });
	      return; // Skip to next token
	    }
	    // Code to deal with attribute selectors
	    if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
	      var tagName = RegExp.$1;
	      var attrName = RegExp.$2;
	      var attrOperator = RegExp.$3;
	      var attrValue = RegExp.$4;
	      if (!tagName) tagName = '*';

	      // Grab all of the tagName elements within current context
	      var found = new Array;
	      var foundCount = 0;
              currentContext.each(function(context){
	        var elements = (tagName == '*') ? getAllChildren(context):context.getElementsByTagName(tagName);
                js2k.toArray(elements).each(function(element){found[foundCount++] = element;});
	      });
	      currentContext = new Array;
	      var currentContextIndex = 0;
	      var checkFunction; // This function will be used to filter the elements
	      switch (attrOperator) {
	        case '=': // Equality
	          checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
	          break;
	        case '~': // Match one of space seperated words
	          checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
	          break;
	        case '|': // Match start with value followed by optional hyphen
	          checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
	          break;
	        case '^': // Match starts with value
	          checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
	          break;
	        case '$': // Match ends with value - fails with "Warning" in Opera 7
	          checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
	          break;
	        case '*': // Match ends with value
	          checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
	          break;
	        default :
	          // Just test for existence of attribute
	          checkFunction = function(e) { return e.getAttribute(attrName); };
	      }
	      currentContext = new Array;
	      var currentContextIndex = 0;
              found.each(function(found){
	        if (checkFunction(found)) currentContext[currentContextIndex++] = found;
	      });
	      // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
	      return; // Skip to next token
	    }
	    // If we get here, token is JUST an element (not a class or ID selector)
	    tagName = token;
	    var found = new Array;
	    var foundCount = 0;
            currentContext.each(function(context){
	      var elements = context.getElementsByTagName(tagName);
              js2k.toArray(elements).each(function(element){found[foundCount++] = element;});
	    });
	    currentContext = found;
	  });
	  return currentContext;

    },
    extend: function(methods, el){
        var el = el || this;
        for(var x in methods) el[x] =  methods[x];
        return el;
    },
    toDOM: function(html){
        var returnElements = [];
        js2k.create("div",{innerHTML: html});
        var nodes = div.childNodes;
		nodes.each(function(el){returnElements.push(el);});
		return returnElements[0];
    },
    create: function(type,opt,parent){
		var el = document.createElement(type) ;
		if (opt.style)
			extend(opt.style,el.style);
		delete opt.style;
		extend(opt,el);
		if (parent)
			parent.appendChild(el);
		return el;
    },
    addJS: function(src, oload){
        var s = this.create("script",{src: src, type: "text/javascript"});
        if (oload) s.onload = s.onreadystatechange =  oload;
        return this.getTagName("head")[0].appendChild(s);
    },
    addMethod: function(name, method, Elem){
        var Elem = Elem || this;
	Elem[name] = method;
    },
    toArray: function(els){
        return Array.prototype.slice.call( els );
    },
    get: function(url, queryString, func, fError) {
        this.request("GET",url, queryString,func,fError);
    },
    post: function(url, queryString, func,fError) {
        this.request("POST",url,queryString, func,fError);
    },
    request: function(method, url, queryString, func,fError) {
        var oAjax = (XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');
        if (!oAjax) return;
        if ("GET" == method) {
          oAjax.open("GET", url + queryString, true);
        } else {
          oAjax.open("POST", url, true);
          oAjax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        }
        oAjax.onreadystatechange = function() {
          if (oAjax.readyState == 4) {
            if (oAjax.status == 200) fn = (func == null)?function() {return null;}:func;
            else fn = (fError == null)?function() {return null;}:fError;
            fn(oAjax.responseXML,oAjax.responseText,oAjax);
          }
        };
        oAjax.send(queryString);
        return false;
    }
};

/*
Document: Array.js
Created on: 03-dic-2008, 11:08:16
Version: 0.1
Author: Andrés Nieto Porras
Description: Recopilación de funciones que nos permiten extender los elementos array.
 */

js2k.extend({
/*
Property: each
        Iterates through an array; This method is only available for browsers without native *forEach* support.
        For more info see <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach>

        *forEach* executes the provided function (callback) once for each element present in the array. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

Arguments:
        - fn:  function to execute with each item in the array; passed the item and the index of that item in the array
        - bind: the object to bind "this" to (see <Function.bind>)

Returns:
		a new array which is a copy of the current one.
Example:
        >['apple','banana','lemon'].each(function(item, index){
        >	alert(index + " = " + item); //alerts "0 = apple" etc.
        >}, bindObj); //optional second arg for binding, not used here
*/
 each: function(fun){
      if (typeof fun != "function") throw new TypeError();
      for (var i = 0; i < this.length; i++) {
        if (i in this) fun.call(arguments[1], this[i], i)
      }
    return this;
 },
 /*
Property: item
        Iterates through an array; This method is only available for browsers without native *forEach* support.
        For more info see <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach>

        *forEach* executes the provided function (callback) once for each element present in the array. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

Arguments:
        - fn:  function to execute with each item in the array; passed the item and the index of that item in the array
        - bind: the object to bind "this" to (see <Function.bind>)

Returns:
		a new array which is a copy of the current one.
Example:
        >['apple','banana','lemon'].each(function(item, index){
        >	alert(index + " = " + item); //alerts "0 = apple" etc.
        >}, bindObj); //optional second arg for binding, not used here
*/
 item: function(x){
      return this[x] || this;
 },
 /*
Property: indexOf
        Iterates through an array; This method is only available for browsers without native *forEach* support.
        For more info see <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach>

        *forEach* executes the provided function (callback) once for each element present in the array. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

Arguments:
        - fn:  function to execute with each item in the array; passed the item and the index of that item in the array

Returns:
		a new array which is a copy of the current one.
Example:
        >['apple','banana','lemon'].each(function(item, index){
        >	alert(index + " = " + item); //alerts "0 = apple" etc.
        >}, bindObj); //optional second arg for binding, not used here
*/
 indexOf: function(s){
      for (var x=0;x<this.length;x++)
          if(this[x] == s) return x;
      return false;
 },
 /*
Property: sortNum
        Iterates through an array; This method is only available for browsers without native *forEach* support.
        For more info see <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach>

        *forEach* executes the provided function (callback) once for each element present in the array. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

Arguments:

Returns:
		a new array which is a copy of the current one.
Example:
        >['apple','banana','lemon'].each(function(item, index){
        >	alert(index + " = " + item); //alerts "0 = apple" etc.
        >}, bindObj); //optional second arg for binding, not used here
*/
 sortNum: function(){
      return this.sort( function (a,b) { return a-b; } );
 },
 /*
Property: every
        Iterates through an array; This method is only available for browsers without native *forEach* support.
        For more info see <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach>

        *forEach* executes the provided function (callback) once for each element present in the array. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

Arguments:
        - num:  function to execute with each item in the array; passed the item and the index of that item in the array
        - fun: the object to bind "this" to (see <Function.bind>)

Returns:
		a new array which is a copy of the current one.
Example:
        >['apple','banana','lemon'].each(function(item, index){
        >	alert(index + " = " + item); //alerts "0 = apple" etc.
        >}, bindObj); //optional second arg for binding, not used here
*/
 every: function(num, fun){
      if (typeof fun != "function") throw new TypeError();
      for (var i = 0; i < this.length; i++) {
        if (i in this && i%num == 0) fun.call(arguments[1], this[i], i)
      }
    return this;
 }
}, Array.prototype);

// Extendemos String
js2k.extend({
  trim: function(){
      return this.replace(/^\s+|\s+$/g,'');
  },
  capitalize: function(){
      return this[0].toUpperCase() + this.substr(1, this.length-1);
  }
}, String.prototype);


// Extendemos Funciones
js2k.extend({
    delay: function(ms, arguments){
        window.setTimeout(this,ms, arguments);
    }
}, Function.prototype);


// Extendemos Elementos
js2k.extend({
  remove: function(){
    if(!this.parentNode) return this;
    this.parentNode.removeChild(this);
  },
  replace: function(node){
    if(!this.parentNode) return this;
    this.parentNode.insertBefore(node,this);
    this.remove();
    return this;
  },
  parent: function(){
    if (!this.parentNode) return this;
    if (arguments.length == 0) {
        return this.get(this.parentNode);
    } else {
    if (this.parentNode.tagName == (arguments[0]).toUpperCase())
        return this.get(this.parentNode);
    else
        this.get(this.parentNode).parent(arguments[0]);
    }
  },
  insert: function(what) {
      if (typeof what == "string") what = js2k.toDOM(what);
      return this.appendChild(what);
  },
  insertText: function(text) {
      return this.innerHTML = text;
  },
  addEvent: function(ev, func, useCapture){
      if (this.addEventListener) {
            this.addEventListener(ev, func, useCapture);
            return true;
		}else if (this.attachEvent) {
            var r = this;
            this.attachEvent('on' + ev, func);
            return r;
		}else {
            this['on' + ev] = func;
		}
 },
 getClassName: function(klass){
     js2k.getClassName(klass,this);
 },
 css: function(){
     if (arguments.length == 1) {
         var a = arguments[0];
	 if (typeof a == 'string') {return this.style[a];}
	 else {for (var p in a) this.style[p] = a[p];}
     }else {
         for(var i = 0, l = arguments.length;i<l;) {
             this.style[arguments[i++]] = arguments[i++];
         }
     }
     return this;
 },
 attr: function(name, value) {
     if (typeof name == 'object') {
         for (var x in name) this.attr(x, name[x]);
         return this;
     }
     if (!value) return this.getAttribute(name);
     this.setAttribute(name, value);
     return this;
 },
 addClass: function(cl){
     if (!this.attr) return false;
     if(this.hasClass(cl)) return this;
     var classes = this.attr('class');
     if(classes==null) this.attr('class',cl);
     else this.attr('class',classes+" "+cl);
     return this;
 },
 removeClass: function(cl){
     if (!this.attr) return false;
     var classes = this.attr('class');
     if(classes==null) return this;
     var arrClasses=classes.split(" ");
     for(var i=0;i<arrClasses.length;i++){
         if(arrClasses[i]==cl){
             arrClasses.splice(i,1);
             break;
         }
     }
     this.attr('class',arrClasses.join(" "));
     return this;
 },
 hasClass: function(cl){
     if (!this.attr) return false;
     var classes=this.attr('class');
     if(classes==null) return false;
     var arrClasses=classes.split(" ");
     for(var i=0;i<arrClasses.length;i++)
         if(arrClasses[i]==cl) return true;
     return false;
  }
}, HTMLElement.prototype);

